﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using Microsoft.Pex.Framework;

namespace AutomaTones.Model.Automaton_Model {
    /// <author>Anders Øland / anoe@itu.dk</author>
    /// <summary>
    ///   A rule defining how a cell evolves.
    ///   The rule decides a cells next state based on the current state of its neighbours.
    ///   Each rule defines its own neighbourhood.
    ///   The processing is done by a RuleDelegate (which may be a multicast delegate).
    /// </summary>
    [PexClass]
    [Serializable]
    public class Rule {
        /// <summary>
        ///   The number of dimensions this rule work in; MUST match the dimensions of the automaton.
        /// </summary>
        protected internal uint Dimensions { get; private set; }

        /// <summary>
        ///   The delegate type used for the methods which perform the actual rule processing.
        /// </summary>
        /// <param name = "cell">The cell to be processed</param>
        /// <param name="statesOfNeighbours">The states of the cells neighbours</param>
        /// <param name="optional">optional parameters for specialized rules</param>
        /// <returns></returns>
        public delegate int RuleDelegate(
            Cell cell, ref IEnumerable<StateAtCoordinate> statesOfNeighbours, params int[] optional);

        /// <summary>
        ///   The concrete RuleDelagate used by this Rule to process the next state of a cell
        ///   based on the states of the cells in its neighbourghood.
        /// </summary>
        public RuleDelegate MyRuleDelegate { get; set; }

        private List<Position> _neighbourhood = new List<Position>();

        /// <summary>
        ///   The coordinates of the neighbouring cells to be used when this rule is applied.
        ///   Subject cell is always [0 ,0] (in a 2-dim. grid.) and eg. the left neighbnour is [-1, 0]
        /// </summary>
        public List<Position> Neighbourhood {
            get { return _neighbourhood; }
            protected internal set {
                Contract.Requires(null != value);
                Contract.Requires(Contract.ForAll<Position>(value, n => Dimensions == n.Coord.Length));
                Contract.Ensures(Neighbourhood == value);
                _neighbourhood = value;
            }
        }

        /// <summary>
        ///   For storing coordinates of (neighbouring) cells.
        ///   Explicit conversions to and from int[] are supported.
        /// </summary>
        [Serializable]
        public struct Position {
            private int[] _coord;

            public int[] Coord {
                get { return _coord; }
                internal set {
                    Contract.Requires(null != value);
                    Contract.Requires(0 < value.Length);
                    Contract.Ensures(_coord.Equals(value));
                    _coord = value;
                }
            }

            public Position(params int[] coord)
                : this() {
                Contract.Requires(null != coord);
                Contract.Requires(0 < coord.Length);
                Contract.Ensures(null != coord & this.Coord == coord);
                Coord = coord;
            }

            // explicit conversion from Position to int[], useful for clients.
            public static explicit operator int[](Position p) {
                Contract.Requires(null != p.Coord);
                Contract.Ensures(Contract.ForAll(0, p.Coord.Length,
                                                 i => p.Coord[i] == Contract.Result<int[]>()[i]));
                return p.Coord.ToArray();
            }

            // explicit conversion from int[] to Position, useful for clients.
            public static explicit operator Position(int[] c) {
                Contract.Requires(null != c);
                Contract.Requires(0 < c.Length);
                Contract.Ensures(Contract.ForAll(0, c.Length,
                                                 i => c[i] == Contract.Result<Position>().Coord[i]));
                return new Position { Coord = (int[])c.Clone() };
            }

            /// <summary>
            /// Pracical + operator when deciding actual coordinates of neighbours 
            /// (relative to the subject/center cell) 
            /// </summary>
            /// <param name="left"></param>
            /// <param name="right"></param>
            /// <returns></returns>
            public static int[] operator +(Position left, int[] right) {
                Contract.Requires(left.Coord.Length == right.Length);
                int[] result = new int[left.Coord.Length];
                for (int i = 0; i < right.Length; i++)
                    result[i] = right[i] + left.Coord[i];
                return result;
            }

            /// <summary>
            ///   Indicates whether this instance and a specified object are equal.
            ///   Accepts int[] as well as Position.
            /// </summary>
            /// <returns>
            ///   true if <paramref name = "obj" /> is either Position or int[] and represent the same coordinate; otherwise, false.
            /// </returns>
            /// <param name = "obj">Another object to compare to. </param>
            /// <filterpriority>2</filterpriority>
            public override bool Equals(object obj) {
                Contract.Requires(null != obj);
                Contract.Requires((obj is Position & ((Position)obj).Coord.Length > 0 || (obj is int[] & ((int[])obj).Length > 0)));
                if (!(obj is Position || obj is int[])) return false;
                Position p;
                if (obj is Position) p = (Position)obj;
                else {
                    int[] tmp = (int[])obj;
                    p = (Position)tmp;
                }
                return p.Coord.SequenceEqual(_coord);
            }

            public override int GetHashCode() {
                return (_coord != null ? _coord.GetHashCode() : 0);
            }
        }

        /// <summary>
        /// A state associated with a given coordinate. Intended for use with neighbours and rules.
        /// Thus the coordinate must be relative to point [0,0.., 0] 
        /// </summary>
        public struct StateAtCoordinate {
            [NonSerialized]
            private Position _position;

            // ReSharper disable MemberHidesStaticFromOuterClass
            public Position Position {
                // ReSharper restore MemberHidesStaticFromOuterClass
                get { return _position; }
                internal set {
                    Contract.Requires(0 < value.Coord.Length);
                    Contract.Ensures(_position.Equals(value));
                    _position = value;
                }
            }

            public int State { get; internal set; }

            public StateAtCoordinate(Position pos, int state)
                : this() {
                Contract.Requires(0 < pos.Coord.Length);
                Position = pos;
                State = state;
            }
        }

        /// <summary>
        ///   Construct a Rule for a celluar automaton with the given dimensions.
        /// </summary>
        /// <param name = "dimensions"></param>
        /// <param name = "ruleDelegate">The rule function</param>
        public Rule(uint dimensions, RuleDelegate ruleDelegate) {
            Dimensions = dimensions;
            MyRuleDelegate = ruleDelegate;
        }

        /// <summary>
        ///   Apply the method in the instance's RuleDelegate to the given cell.
        /// </summary>
        /// <param name="subject"></param>
        /// <param name = "cellularAutomaton"></param>
        /// <param name="optional">Optional parameters for specialized rules</param>
        /// <returns>The next state of the cell based on current state of its neighbourhood</returns>
        [PexMethod]
        public virtual int ApplyRule(int[] subject, CellularAutomaton cellularAutomaton, params int[] optional) {
            Contract.Requires(null != subject);
            Contract.Requires(0 < Neighbourhood.Count);
            Contract.Ensures(cellularAutomaton.GetCell(subject).State.SpaceMin <= Contract.Result<int>());
            Contract.Ensures(cellularAutomaton.GetCell(subject).State.SpaceMax >= Contract.Result<int>());
            IEnumerable<StateAtCoordinate> statesOfNeighbours =
                (from n in Neighbourhood
                 select new StateAtCoordinate {
                     Position = (Position)n.Coord,
                     State = cellularAutomaton.GetCell(n + subject).State.CurrentState
                 });
            return DispatchToDelegate(cellularAutomaton.GetCell(subject), statesOfNeighbours, optional);
        }

        /// <summary>
        /// This method provides a simple yet not perfect workaround for enforcing contracts on delegates.
        /// It simply wraps the invocation of the delegate with pre- and postconditions.
        /// The postcondition focuses on validating the immutability of the forwarded data.
        /// Primarily to ensure that if we're dealine with a multicast delegate - 
        /// none of its members screw things up for the others.
        /// </summary>
        /// <param name="cell"></param>
        /// <param name="statesOfNeighbours">Call by reference so we can check in the postcondition if it has been modified</param>
        /// <param name="optional"></param>
        /// <returns></returns>
        private int DispatchToDelegate(Cell cell, IEnumerable<StateAtCoordinate> statesOfNeighbours,
                                       params int[] optional) {
            Contract.Requires(null != cell);
            Contract.Requires(null != cell.State);
            Contract.Requires(0 < Neighbourhood.Count);
            Contract.Requires(Neighbourhood.Count == statesOfNeighbours.Count());
            Contract.Requires(Contract.ForAll(statesOfNeighbours, s => Dimensions == s.Position.Coord.Length));
            Contract.Ensures(Contract.OldValue(cell) == cell);
            Contract.Ensures(cell.State.SpaceMin <= Contract.Result<int>());
            Contract.Ensures(cell.State.SpaceMax >= Contract.Result<int>());
            return MyRuleDelegate(cell, ref statesOfNeighbours, optional);
        }

        /// <summary>
        ///   Clear the neighbourhood for this rule.
        /// </summary>
        protected internal void ClearNeighbourhood() {
            _neighbourhood = new List<Position>();
        }

        /// <summary>
        ///   Add a neighbour with the gicen coordinats to the neighbourhood.
        ///   Note: if it is already in the neighbourhood it will not be added again.
        /// </summary>
        /// <param name = "coord"></param>
        protected internal void AddNeighbour(params int[] coord) {
            Contract.Requires(Dimensions == coord.Length);
            Contract.Ensures(Neighbourhood.Contains((Position)coord));
            _neighbourhood.Add((Position)coord);
        }

        [ContractInvariantMethod]
// ReSharper disable UnusedMember.Local
        private void ObjInvariant() {
// ReSharper restore UnusedMember.Local
            Contract.Invariant(0 < Dimensions);
        }
    }

    /// <author>Anders Øland / anoe@itu.dk</author>
    /// <summary>
    /// A specialized rule for use with Wolfram Automata - or other pattern-based rule-type CA's.
    /// </summary>
    [Serializable]
    public class WolframRule : Rule {
        /// <summary>
        /// The Wolfram code for this Wolfram rule.
        /// </summary>
        public int[] WolframCode { get; internal set; }

        public WolframRule(uint dimensions, RuleDelegate ruleDelegate, int[] wolframCode)
            : base(dimensions, ruleDelegate) {
            WolframCode = wolframCode;
        }

        /// <summary>
        ///   Apply the method in the instance's RuleDelegate to the given cell.
        /// </summary>
        /// <param name="subject"></param>
        /// <param name = "cellularAutomaton"></param>
        /// <param name="optional">Optional parameters for specialized rules</param>
        /// <returns>The next state of the cell based on current state of its neighbourhood</returns>
        public override int ApplyRule(int[] subject, CellularAutomaton cellularAutomaton, params int[] optional) {
            return base.ApplyRule(subject, cellularAutomaton, WolframCode);
        }
    }
}